/*
 * dbsize.c
 *		数据库对象大小函数和相关查询
 *
 * Copyright (c) 2002-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *	  src/backend/utils/adt/dbsize.c
 *
 */

#include "postgres.h"

#include <sys/stat.h>

#include "access/htup_details.h"
#include "access/relation.h"
#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_tablespace.h"
#include "commands/dbcommands.h"
#include "commands/tablespace.h"
#include "miscadmin.h"
#include "storage/fd.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/numeric.h"
#include "utils/rel.h"
#include "utils/relfilenodemap.h"
#include "utils/relmapper.h"
#include "utils/syscache.h"

/* 除以二并向外取整 */  
#define half_rounded(x)   (((x) + ((x) < 0 ? -1 : 1)) / 2)

/* 在 pg_size_pretty 函数中使用的单位。 所有单位必须是 2 的幂 */  
struct size_pretty_unit
{
	const char *name;			/* 字节，kB，MB，GB 等 */  
	uint32		limit;			/* 除以此单位转换前的上限的一半取整。 */  
	bool		round;			/* 对此单位进行半取整 */  
	uint8		unitbits;		/* (1 << unitbits) 字节以构成此单位的 1 */  
};

/* 在这里添加单位时也更新 pg_size_bytes 中的错误消息 */  
static const struct size_pretty_unit size_pretty_units[] = {
	{"bytes", 10 * 1024, false, 0},
	{"kB", 20 * 1024 - 1, true, 10},
	{"MB", 20 * 1024 - 1, true, 20},
	{"GB", 20 * 1024 - 1, true, 30},
	{"TB", 20 * 1024 - 1, true, 40},
	{"PB", 20 * 1024 - 1, true, 50},
	{NULL, 0, false, 0}
};

/* 返回目录内容的物理大小，如果目录不存在，则返回 0 */  
static int64 fc_db_dir_size(const char *fc_path)
{
	int64		fc_dirsize = 0;
	struct dirent *fc_direntry;
	DIR		   *fc_dirdesc;
	char		fc_filename[MAXPGPATH * 2];

	fc_dirdesc = AllocateDir(fc_path);

	if (!fc_dirdesc)
		return 0;

	while ((fc_direntry = ReadDir(fc_dirdesc, fc_path)) != NULL)
	{
		struct stat fc_fst;

		CHECK_FOR_INTERRUPTS();

		if (strcmp(fc_direntry->d_name, ".") == 0 ||
			strcmp(fc_direntry->d_name, "..") == 0)
			continue;

		snprintf(fc_filename, sizeof(fc_filename), "%s/%s", fc_path, fc_direntry->d_name);

		if (stat(fc_filename, &fc_fst) < 0)
		{
			if (errno == ENOENT)
				continue;
			else
				ereport(ERROR,
						(errcode_for_file_access(),
						 errmsg("could not stat file \"%s\": %m", fc_filename)));
		}
		fc_dirsize += fc_fst.st_size;
	}

	FreeDir(fc_dirdesc);
	return fc_dirsize;
}

/*   
 * 计算所有表空间中数据库的大小   
 */  
static int64 fc_calculate_database_size(Oid fc_dbOid)
{
	int64		fc_totalsize;
	DIR		   *fc_dirdesc;
	struct dirent *fc_direntry;
	char		fc_dirpath[MAXPGPATH];
	char		fc_pathname[MAXPGPATH + 21 + sizeof(TABLESPACE_VERSION_DIRECTORY)];
	AclResult	fc_aclresult;

	/*   
	 * 用户必须对目标数据库具有连接权限或具有 pg_read_all_stats 的权限   
	 */  
	fc_aclresult = pg_database_aclcheck(fc_dbOid, GetUserId(), ACL_CONNECT);
	if (fc_aclresult != ACLCHECK_OK &&
		!has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
	{
		aclcheck_error(fc_aclresult, OBJECT_DATABASE,
					   get_database_name(fc_dbOid));
	}

	/* pg_global 中的共享存储不计算在内 */  

	/* 包括 pg_default 存储 */  
	snprintf(fc_pathname, sizeof(fc_pathname), "base/%u", fc_dbOid);
	fc_totalsize = fc_db_dir_size(fc_pathname);

	/* 扫描非默认表空间 */  
	snprintf(fc_dirpath, MAXPGPATH, "pg_tblspc");
	fc_dirdesc = AllocateDir(fc_dirpath);

	while ((fc_direntry = ReadDir(fc_dirdesc, fc_dirpath)) != NULL)
	{
		CHECK_FOR_INTERRUPTS();

		if (strcmp(fc_direntry->d_name, ".") == 0 ||
			strcmp(fc_direntry->d_name, "..") == 0)
			continue;

		snprintf(fc_pathname, sizeof(fc_pathname), "pg_tblspc/%s/%s/%u",
				 fc_direntry->d_name, TABLESPACE_VERSION_DIRECTORY, fc_dbOid);
		fc_totalsize += fc_db_dir_size(fc_pathname);
	}

	FreeDir(fc_dirdesc);

	return fc_totalsize;
}

Datum pg_database_size_oid(PG_FUNCTION_ARGS)
{
	Oid			fc_dbOid = PG_GETARG_OID(0);
	int64		fc_size;

	fc_size = fc_calculate_database_size(fc_dbOid);

	if (fc_size == 0)
		PG_RETURN_NULL();

	PG_RETURN_INT64(fc_size);
}

Datum pg_database_size_name(PG_FUNCTION_ARGS)
{
	Name		fc_dbName = PG_GETARG_NAME(0);
	Oid			fc_dbOid = get_database_oid(NameStr(*fc_dbName), false);
	int64		fc_size;

	fc_size = fc_calculate_database_size(fc_dbOid);

	if (fc_size == 0)
		PG_RETURN_NULL();

	PG_RETURN_INT64(fc_size);
}


/*   
 * 计算表空间的总大小。如果找不到表空间目录，则返回 -1。   
 */  
static int64 fc_calculate_tablespace_size(Oid fc_tblspcOid)
{
	char		fc_tblspcPath[MAXPGPATH];
	char		fc_pathname[MAXPGPATH * 2];
	int64		fc_totalsize = 0;
	DIR		   *fc_dirdesc;
	struct dirent *fc_direntry;
	AclResult	fc_aclresult;

	/*   
	 * 用户必须具有 pg_read_all_stats 的权限或具有目标表空间的创建权限，   
	 * 无论是显式授予还是因为它是当前数据库的默认值而隐式授予。   
	 */  
	if (fc_tblspcOid != MyDatabaseTableSpace &&
		!has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
	{
		fc_aclresult = pg_tablespace_aclcheck(fc_tblspcOid, GetUserId(), ACL_CREATE);
		if (fc_aclresult != ACLCHECK_OK)
			aclcheck_error(fc_aclresult, OBJECT_TABLESPACE,
						   get_tablespace_name(fc_tblspcOid));
	}

	if (fc_tblspcOid == DEFAULTTABLESPACE_OID)
		snprintf(fc_tblspcPath, MAXPGPATH, "base");
	else if (fc_tblspcOid == GLOBALTABLESPACE_OID)
		snprintf(fc_tblspcPath, MAXPGPATH, "global");
	else
		snprintf(fc_tblspcPath, MAXPGPATH, "pg_tblspc/%u/%s", fc_tblspcOid,
				 TABLESPACE_VERSION_DIRECTORY);

	fc_dirdesc = AllocateDir(fc_tblspcPath);

	if (!fc_dirdesc)
		return -1;

	while ((fc_direntry = ReadDir(fc_dirdesc, fc_tblspcPath)) != NULL)
	{
		struct stat fc_fst;

		CHECK_FOR_INTERRUPTS();

		if (strcmp(fc_direntry->d_name, ".") == 0 ||
			strcmp(fc_direntry->d_name, "..") == 0)
			continue;

		snprintf(fc_pathname, sizeof(fc_pathname), "%s/%s", fc_tblspcPath, fc_direntry->d_name);

		if (stat(fc_pathname, &fc_fst) < 0)
		{
			if (errno == ENOENT)
				continue;
			else
				ereport(ERROR,
						(errcode_for_file_access(),
						 errmsg("could not stat file \"%s\": %m", fc_pathname)));
		}

		if (S_ISDIR(fc_fst.st_mode))
			fc_totalsize += fc_db_dir_size(fc_pathname);

		fc_totalsize += fc_fst.st_size;
	}

	FreeDir(fc_dirdesc);

	return fc_totalsize;
}

Datum pg_tablespace_size_oid(PG_FUNCTION_ARGS)
{
	Oid			fc_tblspcOid = PG_GETARG_OID(0);
	int64		fc_size;

	fc_size = fc_calculate_tablespace_size(fc_tblspcOid);

	if (fc_size < 0)
		PG_RETURN_NULL();

	PG_RETURN_INT64(fc_size);
}

Datum pg_tablespace_size_name(PG_FUNCTION_ARGS)
{
	Name		fc_tblspcName = PG_GETARG_NAME(0);
	Oid			fc_tblspcOid = get_tablespace_oid(NameStr(*fc_tblspcName), false);
	int64		fc_size;

	fc_size = fc_calculate_tablespace_size(fc_tblspcOid);

	if (fc_size < 0)
		PG_RETURN_NULL();

	PG_RETURN_INT64(fc_size);
}


/*   
 * 计算一个关系的（一个分支的）大小   
 *   
 * 注意：我们可以安全地将此应用于其他会话的临时表，因此这里或调用点没有检查。   
 */  
static int64 fc_calculate_relation_size(RelFileNode *fc_rfn, BackendId fc_backend, ForkNumber fc_forknum)
{
	int64		fc_totalsize = 0;
	char	   *fc_relationpath;
	char		fc_pathname[MAXPGPATH];
	unsigned int fc_segcount = 0;

	fc_relationpath = relpathbackend(*fc_rfn, fc_backend, fc_forknum);

	for (fc_segcount = 0;; fc_segcount++)
	{
		struct stat fc_fst;

		CHECK_FOR_INTERRUPTS();

		if (fc_segcount == 0)
			snprintf(fc_pathname, MAXPGPATH, "%s",
					 fc_relationpath);
		else
			snprintf(fc_pathname, MAXPGPATH, "%s.%u",
					 fc_relationpath, fc_segcount);

		if (stat(fc_pathname, &fc_fst) < 0)
		{
			if (errno == ENOENT)
				break;
			else
				ereport(ERROR,
						(errcode_for_file_access(),
						 errmsg("could not stat file \"%s\": %m", fc_pathname)));
		}
		fc_totalsize += fc_fst.st_size;
	}

	return fc_totalsize;
}

Datum pg_relation_size(PG_FUNCTION_ARGS)
{
	Oid			fc_relOid = PG_GETARG_OID(0);
	text	   *fc_forkName = PG_GETARG_TEXT_PP(1);
	Relation	fc_rel;
	int64		fc_size;

	fc_rel = try_relation_open(fc_relOid, AccessShareLock);

	/*
	 * 在9.2之前，如果关系不存在，我们会抛出错误，
	 * 但这会使得像“SELECT pg_relation_size(oid) FROM pg_class”
	 * 这样的查询变得不够健壮，因为当我们使用MVCC快照扫描pg_class时，
	 * 可能会有人删除该表。返回NULL对于已经删除的表比抛出错误
	 * 并中止整个查询要好。
	 */
	if (fc_rel == NULL)
		PG_RETURN_NULL();

	fc_size = fc_calculate_relation_size(&(fc_rel->rd_node), fc_rel->rd_backend,
								   forkname_to_number(text_to_cstring(fc_forkName)));

	relation_close(fc_rel, AccessShareLock);

	PG_RETURN_INT64(fc_size);
}

/*
 * 计算TOAST关系的总磁盘大小，包括其索引。
 * 不能应用于非TOAST关系。
 */
static int64 fc_calculate_toast_table_size(Oid fc_toastrelid)
{
	int64		fc_size = 0;
	Relation	fc_toastRel;
	ForkNumber	fc_forkNum;
	ListCell   *fc_lc;
	List	   *fc_indexlist;

	fc_toastRel = relation_open(fc_toastrelid, AccessShareLock);

	/* toast堆大小，包括FSM和VM大小 */
	for (fc_forkNum = 0; fc_forkNum <= MAX_FORKNUM; fc_forkNum++)
		fc_size += fc_calculate_relation_size(&(fc_toastRel->rd_node),
										fc_toastRel->rd_backend, fc_forkNum);

	/* toast索引大小，包括FSM和VM大小 */
	fc_indexlist = RelationGetIndexList(fc_toastRel);

	/* 大小是使用所有可用索引计算的 */
	foreach(fc_lc, fc_indexlist)
	{
		Relation	fc_toastIdxRel;

		fc_toastIdxRel = relation_open(lfirst_oid(fc_lc),
									AccessShareLock);
		for (fc_forkNum = 0; fc_forkNum <= MAX_FORKNUM; fc_forkNum++)
			fc_size += fc_calculate_relation_size(&(fc_toastIdxRel->rd_node),
											fc_toastIdxRel->rd_backend, fc_forkNum);

		relation_close(fc_toastIdxRel, AccessShareLock);
	}
	list_free(fc_indexlist);
	relation_close(fc_toastRel, AccessShareLock);

	return fc_size;
}

/*
 * 计算给定表的总磁盘大小，
 * 包括FSM和VM，如果有TOAST表也计算在内。
 * 除TOAST表的索引外，其他索引不包括在内。
 *
 * 注意，如果对索引或toast表应用，该行为也正常；
 * 这些不会有附加的toast表，但可以有多个分叉。
 */
static int64 fc_calculate_table_size(Relation fc_rel)
{
	int64		fc_size = 0;
	ForkNumber	fc_forkNum;

	/*
	 * 堆大小，包括FSM和VM
	 */
	for (fc_forkNum = 0; fc_forkNum <= MAX_FORKNUM; fc_forkNum++)
		fc_size += fc_calculate_relation_size(&(fc_rel->rd_node), fc_rel->rd_backend,
										fc_forkNum);

	/*
	 * toast关系的大小
	 */
	if (OidIsValid(fc_rel->rd_rel->reltoastrelid))
		fc_size += fc_calculate_toast_table_size(fc_rel->rd_rel->reltoastrelid);

	return fc_size;
}

/*
 * 计算附加到给定表的所有索引的总磁盘大小。
 *
 * 可以安全地应用于索引，但你会得到零。
 */
static int64 fc_calculate_indexes_size(Relation fc_rel)
{
	int64		fc_size = 0;

	/*
	 * 聚合给定关系上的所有索引
	 */
	if (fc_rel->rd_rel->relhasindex)
	{
		List	   *fc_index_oids = RelationGetIndexList(fc_rel);
		ListCell   *fc_cell;

		foreach(fc_cell, fc_index_oids)
		{
			Oid			fc_idxOid = lfirst_oid(fc_cell);
			Relation	fc_idxRel;
			ForkNumber	fc_forkNum;

			fc_idxRel = relation_open(fc_idxOid, AccessShareLock);

			for (fc_forkNum = 0; fc_forkNum <= MAX_FORKNUM; fc_forkNum++)
				fc_size += fc_calculate_relation_size(&(fc_idxRel->rd_node),
												fc_idxRel->rd_backend,
												fc_forkNum);

			relation_close(fc_idxRel, AccessShareLock);
		}

		list_free(fc_index_oids);
	}

	return fc_size;
}

Datum pg_table_size(PG_FUNCTION_ARGS)
{
	Oid			fc_relOid = PG_GETARG_OID(0);
	Relation	fc_rel;
	int64		fc_size;

	fc_rel = try_relation_open(fc_relOid, AccessShareLock);

	if (fc_rel == NULL)
		PG_RETURN_NULL();

	fc_size = fc_calculate_table_size(fc_rel);

	relation_close(fc_rel, AccessShareLock);

	PG_RETURN_INT64(fc_size);
}

Datum pg_indexes_size(PG_FUNCTION_ARGS)
{
	Oid			fc_relOid = PG_GETARG_OID(0);
	Relation	fc_rel;
	int64		fc_size;

	fc_rel = try_relation_open(fc_relOid, AccessShareLock);

	if (fc_rel == NULL)
		PG_RETURN_NULL();

	fc_size = fc_calculate_indexes_size(fc_rel);

	relation_close(fc_rel, AccessShareLock);

	PG_RETURN_INT64(fc_size);
}

/*
 * 计算该关系所有文件的磁盘大小，
 * 包括堆数据、索引数据、toast数据、FSM、VM。
 */
static int64 fc_calculate_total_relation_size(Relation fc_rel)
{
	int64		fc_size;

	/*
	 * 聚合表大小，包括堆、toast和
	 * toast索引以及空闲空间和可见性映射的大小
	 */
	fc_size = fc_calculate_table_size(fc_rel);

	/*
	 * 还添加所有附加索引的大小
	 */
	fc_size += fc_calculate_indexes_size(fc_rel);

	return fc_size;
}

Datum pg_total_relation_size(PG_FUNCTION_ARGS)
{
	Oid			fc_relOid = PG_GETARG_OID(0);
	Relation	fc_rel;
	int64		fc_size;

	fc_rel = try_relation_open(fc_relOid, AccessShareLock);

	if (fc_rel == NULL)
		PG_RETURN_NULL();

	fc_size = fc_calculate_total_relation_size(fc_rel);

	relation_close(fc_rel, AccessShareLock);

	PG_RETURN_INT64(fc_size);
}

/*
 * 带大小单位的格式化
 */
Datum pg_size_pretty(PG_FUNCTION_ARGS)
{
	int64		fc_size = PG_GETARG_INT64(0);
	char		fc_buf[64];
	const struct size_pretty_unit *fc_unit;

	for (fc_unit = size_pretty_units; fc_unit->name != NULL; fc_unit++)
	{
		uint8		fc_bits;
		uint64		fc_abs_size = fc_size < 0 ? 0 - (uint64) fc_size : (uint64) fc_size;

		/*
		 * 如果没有更多单位或绝对大小低于
		 * 当前单位的限制，使用此单位。
		 */
		if (fc_unit[1].name == NULL || fc_abs_size < fc_unit->limit)
		{
			if (fc_unit->round)
				fc_size = half_rounded(fc_size);

			snprintf(fc_buf, sizeof(fc_buf), INT64_FORMAT " %s", fc_size, fc_unit->name);
			break;
		}

		/*
		 * 确定用于构建除数的位数。如果下一个单位使用半舍入，
		 * 我们可能需要使用比这个和下一个单位之间的差值小1位。
		 * 或者如果该单位使用半舍入而下一个单位不使用，
		 * 我们可能需要多移动1位。我们使用除法而不是右移
		 * 这些位以确保正值和负值以相同方式舍入。
		 */
		fc_bits = (fc_unit[1].unitbits - fc_unit->unitbits - (fc_unit[1].round == true)
				+ (fc_unit->round == true));
		fc_size /= ((int64) 1) << fc_bits;
	}

	PG_RETURN_TEXT_P(cstring_to_text(fc_buf));
}

static char * fc_numeric_to_cstring(Numeric fc_n)
{
	Datum		fc_d = NumericGetDatum(fc_n);

	return DatumGetCString(DirectFunctionCall1(numeric_out, fc_d));
}

static bool fc_numeric_is_less(Numeric fc_a, Numeric fc_b)
{
	Datum		fc_da = NumericGetDatum(fc_a);
	Datum		fc_db = NumericGetDatum(fc_b);

	return DatumGetBool(DirectFunctionCall2(numeric_lt, fc_da, fc_db));
}

static Numeric fc_numeric_absolute(Numeric fc_n)
{
	Datum		fc_d = NumericGetDatum(fc_n);
	Datum		fc_result;

	fc_result = DirectFunctionCall1(numeric_abs, fc_d);
	return DatumGetNumeric(fc_result);
}

static Numeric fc_numeric_half_rounded(Numeric fc_n)
{
	Datum		fc_d = NumericGetDatum(fc_n);
	Datum		fc_zero;
	Datum		fc_one;
	Datum		fc_two;
	Datum		fc_result;

	fc_zero = NumericGetDatum(int64_to_numeric(0));
	fc_one = NumericGetDatum(int64_to_numeric(1));
	fc_two = NumericGetDatum(int64_to_numeric(2));

	if (DatumGetBool(DirectFunctionCall2(numeric_ge, fc_d, fc_zero)))
		fc_d = DirectFunctionCall2(numeric_add, fc_d, fc_one);
	else
		fc_d = DirectFunctionCall2(numeric_sub, fc_d, fc_one);

	fc_result = DirectFunctionCall2(numeric_div_trunc, fc_d, fc_two);
	return DatumGetNumeric(fc_result);
}

static Numeric fc_numeric_truncated_divide(Numeric fc_n, int64 fc_divisor)
{
	Datum		fc_d = NumericGetDatum(fc_n);
	Datum		fc_divisor_numeric;
	Datum		fc_result;

	fc_divisor_numeric = NumericGetDatum(int64_to_numeric(fc_divisor));
	fc_result = DirectFunctionCall2(numeric_div_trunc, fc_d, fc_divisor_numeric);
	return DatumGetNumeric(fc_result);
}

Datum pg_size_pretty_numeric(PG_FUNCTION_ARGS)
{
	Numeric		fc_size = PG_GETARG_NUMERIC(0);
	char	   *fc_result = NULL;
	const struct size_pretty_unit *fc_unit;

	for (fc_unit = size_pretty_units; fc_unit->name != NULL; fc_unit++)
	{
		unsigned int fc_shiftby;

		/* 如果没有更多单位或我们低于限制，则使用此单位 */
		if (fc_unit[1].name == NULL ||
			fc_numeric_is_less(fc_numeric_absolute(fc_size),
							int64_to_numeric(fc_unit->limit)))
		{
			if (fc_unit->round)
				fc_size = fc_numeric_half_rounded(fc_size);

			fc_result = psprintf("%s %s", fc_numeric_to_cstring(fc_size), fc_unit->name);
			break;
		}

		/*
		 * 确定用于构建除数的位数。如果下一个单位使用半舍入，
		 * 我们可能需要使用比这个和下一个单位之间的差值小1位。
		 * 或者如果该单位使用半舍入而下一个单位不使用，
		 * 我们可能需要多移动1位。
		 */
		fc_shiftby = (fc_unit[1].unitbits - fc_unit->unitbits - (fc_unit[1].round == true)
				   + (fc_unit->round == true));
		fc_size = fc_numeric_truncated_divide(fc_size, ((int64) 1) << fc_shiftby);
	}

	PG_RETURN_TEXT_P(cstring_to_text(fc_result));
}

/*
 * 将人类可读的大小转换为以字节为单位的大小
 */
Datum pg_size_bytes(PG_FUNCTION_ARGS)
{
	text	   *fc_arg = PG_GETARG_TEXT_PP(0);
	char	   *fc_str,
			   *fc_strptr,
			   *fc_endptr;
	char		fc_saved_char;
	Numeric		fc_num;
	int64		fc_result;
	bool		fc_have_digits = false;

	fc_str = text_to_cstring(fc_arg);

	/* 跳过前导空格 */
	fc_strptr = fc_str;
	while (isspace((unsigned char) *fc_strptr))
		fc_strptr++;

	/* 检查我们是否有有效数字并确定其结束位置 */
	fc_endptr = fc_strptr;

	/* 部分(1)：符号 */
	if (*fc_endptr == '-' || *fc_endptr == '+')
		fc_endptr++;

	/* 部分(2)：主要数字字符串 */
	if (isdigit((unsigned char) *fc_endptr))
	{
		fc_have_digits = true;
		do
			fc_endptr++;
		while (isdigit((unsigned char) *fc_endptr));
	}

	/* 部分(3)：可选的小数点和小数位 */
	if (*fc_endptr == '.')
	{
		fc_endptr++;
		if (isdigit((unsigned char) *fc_endptr))
		{
			fc_have_digits = true;
			do
				fc_endptr++;
			while (isdigit((unsigned char) *fc_endptr));
		}
	}

	/* 如果此时没有有效数字，则进行投诉 */
	if (!fc_have_digits)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("invalid size: \"%s\"", fc_str)));

	/* 部分(4)：可选的指数 */
	if (*fc_endptr == 'e' || *fc_endptr == 'E')
	{
		long		fc_exponent;
		char	   *fc_cp;

		/*
		 * 请注意，我们可能有一天会支持EB单位，
		 * 因此，如果'E'后面的不是数字，只需将其视为要解析的单位。
		 */
		fc_exponent = strtol(fc_endptr + 1, &fc_cp, 10);
		(void) fc_exponent;		/* 消除 -Wunused-result 警告 */
		if (fc_cp > fc_endptr + 1)
			fc_endptr = fc_cp;
	}

	/*
	 * 解析数字，保存下一个字符，这可能是单位字符串的第一个
	 * 字符。
	 */
	fc_saved_char = *fc_endptr;
	*fc_endptr = '\0';

	fc_num = DatumGetNumeric(DirectFunctionCall3(numeric_in,
											  CStringGetDatum(fc_strptr),
											  ObjectIdGetDatum(InvalidOid),
											  Int32GetDatum(-1)));

	*fc_endptr = fc_saved_char;

	/* 跳过数字和单位之间的空白 */
	fc_strptr = fc_endptr;
	while (isspace((unsigned char) *fc_strptr))
		fc_strptr++;

	/*
 * 以字符串形式获取选项 `name` 的当前值。
 *
 * 如果选项不存在，如果 missing_ok 为 true 则返回 NULL（注意这
 * 无法与值为 NULL 的字符串变量区分！），否则抛出一个 ereport
 * 并不返回。
 *
 * 如果 restrict_privileged 为 true，我们还会强制只有超级用户和
 * pg_read_all_settings 角色的成员可以看到 GUC_SUPERUSER_ONLY
 * 变量。 这仅应在用户驱动的调用中传递为 true。
 *
 * 字符串 *不是* 为修改而分配的，实际上只在下一个调用配置相关函数之前有效。
 */
	if (*fc_strptr != '\0')
	{
		const struct size_pretty_unit *fc_unit;
		int64		fc_multiplier = 0;

		/* 修剪尾随空格 */
		fc_endptr = fc_str + VARSIZE_ANY_EXHDR(fc_arg) - 1;

		while (isspace((unsigned char) *fc_endptr))
			fc_endptr--;

		fc_endptr++;
		*fc_endptr = '\0';

		for (fc_unit = size_pretty_units; fc_unit->name != NULL; fc_unit++)
		{
			/* 解析单位时不区分大小写 */
			if (pg_strcasecmp(fc_strptr, fc_unit->name) == 0)
			{
				fc_multiplier = ((int64) 1) << fc_unit->unitbits;
				break;
			}
		}

		/* 验证我们是否在上面的循环中找到了有效的单位 */
		if (fc_unit->name == NULL)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("invalid size: \"%s\"", text_to_cstring(fc_arg)),
					 errdetail("Invalid size unit: \"%s\".", fc_strptr),
					 errhint("Valid units are \"bytes\", \"kB\", \"MB\", \"GB\", \"TB\", and \"PB\".")));

		if (fc_multiplier > 1)
		{
			Numeric		fc_mul_num;

			fc_mul_num = int64_to_numeric(fc_multiplier);

			fc_num = DatumGetNumeric(DirectFunctionCall2(numeric_mul,
													  NumericGetDatum(fc_mul_num),
													  NumericGetDatum(fc_num)));
		}
	}

	fc_result = DatumGetInt64(DirectFunctionCall1(numeric_int8,
											   NumericGetDatum(fc_num)));

	PG_RETURN_INT64(fc_result);
}

/*
 * 获取关系的文件节点
 *
 * 这应该在类似于以下查询中使用
 *		SELECT pg_relation_filenode(oid) FROM pg_class;
 * 这导致了几个选择。我们仅从 pg_class 行开始工作
 * 而不是实际打开每个关系，以提高效率。如果我们找不到关系，
 * 我们不会失败——某些行可能在查询的 MVCC 快照中可见，
 * 即使这些关系已被删除。
 * （注意：我们可以避免使用 catcache，但这样做没有太大意义，
 * 因为关系映射器也“在当下”工作。）如果关系没有存储，
 * 我们也不会失败。在所有这些情况下，似乎更好的是静默返回 NULL。
 */
Datum pg_relation_filenode(PG_FUNCTION_ARGS)
{
	Oid			fc_relid = PG_GETARG_OID(0);
	Oid			fc_result;
	HeapTuple	fc_tuple;
	Form_pg_class fc_relform;

	fc_tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(fc_relid));
	if (!HeapTupleIsValid(fc_tuple))
		PG_RETURN_NULL();
	fc_relform = (Form_pg_class) GETSTRUCT(fc_tuple);

	if (RELKIND_HAS_STORAGE(fc_relform->relkind))
	{
		if (fc_relform->relfilenode)
			fc_result = fc_relform->relfilenode;
		else					/* 查阅关系映射器 */
			fc_result = RelationMapOidToFilenode(fc_relid,
											  fc_relform->relisshared);
	}
	else
	{
		/* 无存储，返回 NULL */
		fc_result = InvalidOid;
	}

	ReleaseSysCache(fc_tuple);

	if (!OidIsValid(fc_result))
		PG_RETURN_NULL();

	PG_RETURN_OID(fc_result);
}

/*
 * 通过 (reltablespace, relfilenode) 获取关系
 *
 * 这预计在某人想将文件系统上的单个文件匹配回其表时使用。
 * 通过 pg_class 这并不容易，因为它不包含共享和固定表的 relfilenodes。
 *
 * 如果我们找不到映射，我们不会失败，而是返回 NULL。
 *
 * InvalidOid 可以替代当前数据库的默认表空间传递。
 */
Datum pg_filenode_relation(PG_FUNCTION_ARGS)
{
	Oid			fc_reltablespace = PG_GETARG_OID(0);
	Oid			fc_relfilenode = PG_GETARG_OID(1);
	Oid			fc_heaprel;

	/* 需要测试，所以 RelidByRelfilenode 不会失常 */
	if (!OidIsValid(fc_relfilenode))
		PG_RETURN_NULL();

	fc_heaprel = RelidByRelfilenode(fc_reltablespace, fc_relfilenode);

	if (!OidIsValid(fc_heaprel))
		PG_RETURN_NULL();
	else
		PG_RETURN_OID(fc_heaprel);
}

/*
 * 获取关系的路径名（相对于 $PGDATA）
 *
 * 请查看 pg_relation_filenode 的注释。
 */
Datum pg_relation_filepath(PG_FUNCTION_ARGS)
{
	Oid			fc_relid = PG_GETARG_OID(0);
	HeapTuple	fc_tuple;
	Form_pg_class fc_relform;
	RelFileNode fc_rnode;
	BackendId	fc_backend;
	char	   *fc_path;

	fc_tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(fc_relid));
	if (!HeapTupleIsValid(fc_tuple))
		PG_RETURN_NULL();
	fc_relform = (Form_pg_class) GETSTRUCT(fc_tuple);

	if (RELKIND_HAS_STORAGE(fc_relform->relkind))
	{
		/* 这个逻辑应该与 RelationInitPhysicalAddr 匹配 */
		if (fc_relform->reltablespace)
			fc_rnode.spcNode = fc_relform->reltablespace;
		else
			fc_rnode.spcNode = MyDatabaseTableSpace;
		if (fc_rnode.spcNode == GLOBALTABLESPACE_OID)
			fc_rnode.dbNode = InvalidOid;
		else
			fc_rnode.dbNode = MyDatabaseId;
		if (fc_relform->relfilenode)
			fc_rnode.relNode = fc_relform->relfilenode;
		else					/* 查阅关系映射器 */
			fc_rnode.relNode = RelationMapOidToFilenode(fc_relid,
													 fc_relform->relisshared);
	}
	else
	{
		/* 无存储，返回 NULL */
		fc_rnode.relNode = InvalidOid;
		/* 一些编译器在没有以下两行时会生成警告 */
		fc_rnode.dbNode = InvalidOid;
		fc_rnode.spcNode = InvalidOid;
	}

	if (!OidIsValid(fc_rnode.relNode))
	{
		ReleaseSysCache(fc_tuple);
		PG_RETURN_NULL();
	}

	/* 确定所有者后台。 */
	switch (fc_relform->relpersistence)
	{
		case RELPERSISTENCE_UNLOGGED:
		case RELPERSISTENCE_PERMANENT:
			fc_backend = InvalidBackendId;
			break;
		case RELPERSISTENCE_TEMP:
			if (isTempOrTempToastNamespace(fc_relform->relnamespace))
				fc_backend = BackendIdForTempRelations();
			else
			{
				/* 用困难的方法做。 */
				fc_backend = GetTempNamespaceBackendId(fc_relform->relnamespace);
				Assert(fc_backend != InvalidBackendId);
			}
			break;
		default:
			elog(ERROR, "invalid relpersistence: %c", fc_relform->relpersistence);
			fc_backend = InvalidBackendId; /* 安抚编译器 */
			break;
	}

	ReleaseSysCache(fc_tuple);

	fc_path = relpathbackend(fc_rnode, fc_backend, MAIN_FORKNUM);

	PG_RETURN_TEXT_P(cstring_to_text(fc_path));
}
